#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

from enum import Enum
import json
import socket
import ssl
import os
import urllib
from urllib import request
import urllib3
from urllib3.exceptions import HTTPError
from urllib3.util import parse_url

from common.cleaner import clear
from oracle.common.device_manager.constants import HttpConfig, HttpConfigOnce
from oracle import logger


class CertSource(str, Enum):
    INTERNAL = 'internal'
    EXTERNAL = 'external'


class HttpRequest:
    def __init__(self):
        self.method = ''
        self.host = ''
        self.port = ''
        self.suffix = ''
        self.body = None
        self.headers = {}
        self.cookie = None
        self.https = False
        self.cert_source = CertSource.INTERNAL
        self.verify = True
        self.proxy = None


class RequestUtils(object):
    def __init__(self, req: HttpRequest, cert_path: str, crl_path: str, retry_flag: bool = True):
        self.req = req
        self.verify = False if not self.req.https else int(self.req.verify) == 1
        self.rsp = None
        self.key_pass = ''
        self.cert_path = cert_path
        self.crl_path = crl_path
        self.retry_flag = retry_flag

    @staticmethod
    def _get_http_instance_with_no_verify(req: HttpRequest, retry_flag: bool):
        if req.proxy:
            proxy = parse_url(req.proxy)
        else:
            proxy = None
        retries = HttpConfig.MAX_HTTP_RETRIES
        if not retry_flag:
            retries = HttpConfigOnce.MAX_HTTP_RETRIES
        if req.https:
            http = urllib3.HTTPSConnectionPool(req.host, req.port, maxsize=HttpConfig.MAX_CACHE_CONNECTION,
                                               retries=retries, timeout=HttpConfig.HTTP_TIME_OUT,
                                               cert_reqs='CERT_NONE', assert_hostname=False,
                                               _proxy=proxy)
        else:
            http = urllib3.HTTPConnectionPool(req.host, req.port, maxsize=HttpConfig.MAX_CACHE_CONNECTION,
                                              retries=retries, timeout=HttpConfig.HTTP_TIME_OUT,
                                              _proxy=proxy)
        return http

    @staticmethod
    def _is_json(string):
        try:
            json.loads(string)
        except ValueError:
            return False
        return True

    @staticmethod
    def _is_ipv6(ip):
        try:
            socket.inet_pton(socket.AF_INET6, ip)
        except socket.error:
            return False
        return True

    @staticmethod
    def _get_ssl_context(ca_path, crl_path):
        try:
            logger.debug(f'start get ssl content')
            ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
            ssl_context.verify_mode = ssl.CERT_REQUIRED
            ssl_context.check_hostname = False
            ssl_context.set_ciphers("DEFAULT:@SECLEVEL=0")
            if os.path.exists(crl_path):
                logger.debug(f'Set crl_path...')
                ssl_context.verify_flags = ssl.VERIFY_CRL_CHECK_CHAIN
                ssl_context.load_verify_locations(crl_path)
            else:
                logger.debug(f'No need load crl, path not exist or expired')
            ssl_context.load_verify_locations(ca_path)
            return ssl_context
        except Exception as e:
            logger.error(e, exc_info=True)
            return ''

    def send_request(self):
        if self.verify:
            # 开启证书认证
            return self.send_request_with_verify()
        else:
            return self.send_request_with_no_verify()

    def send_request_with_no_verify(self):
        http = self._get_http_instance_with_no_verify(self.req, self.retry_flag)
        if self.req.cookie:
            self.req.headers['Cookie'] = self.req.cookie
        logger.info(f'Method: {self.req.method} Url: {self._get_url_from_http_req(self.req)}')
        try:
            self.rsp = http.request(self.req.method, self.req.suffix, headers=self.req.headers, body=self.req.body)
        except HTTPError as ex:
            logger.error('Fail to send request!', exc_info=True)
            raise ex
        finally:
            clear(self.key_pass)

        rsp_data = self.rsp.data.decode('utf8')
        data = json.loads(rsp_data) if self._is_json(rsp_data) else rsp_data
        return self.rsp.status, data

    def send_request_with_verify(self):
        context = RequestUtils._get_ssl_context(self.cert_path, self.crl_path)
        if not context:
            logger.error(f'get ssl content failed')
            raise Exception('get ssl content failed')
        try:
            req = request.Request(url=self._get_url_from_http_req(self.req), data=bytes(self.req.body, 'utf8'),
                                  method=self.req.method, headers=self.req.headers)
            # 全局设置代理，由于插件python进程不是常驻的，所以此处修改不会影响到其他备份任务
            if self.req.proxy:
                proxy_handler = urllib.request.ProxyHandler({'https': self.req.proxy})
                opener = request.build_opener(proxy_handler)
                request.install_opener(opener)
            self.rsp = request.urlopen(req, timeout=HttpConfig.HTTP_TIME_OUT, context=context)
        except Exception as ex:
            logger.error(f'send http request failed')
            raise ex
        status, rsp_data = self.rsp.getcode(), self.rsp.read().decode('utf-8')
        data = json.loads(rsp_data) if RequestUtils._is_json(rsp_data) else rsp_data
        return status, data

    def get_headers(self):
        return self.rsp.headers if self.rsp else None

    def _get_url_from_http_req(self, req):
        if self._is_ipv6(req.host):
            url = f"[{req.host}]:{req.port}{req.suffix}"
        else:
            url = f"{req.host}:{req.port}{req.suffix}"
        if req.https:
            return f"https://{url}"
        else:
            return f"http://{url}"
